package com.space.components.domain.core.diff;

import com.space.components.domain.core.Entity;
import com.space.components.domain.core.annotation.EnableEntityDiff;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author <a href="mailto:414563035@qq.com">Bedroom</a>
 * @date 2021/5/24 5:08 下午
 */
public class EntityDiffUtils {
    private static final Map<Class<?>, Map<String, Field>> CACHE = new ConcurrentHashMap<>();
    private static final Map<Class<?>, EnableEntityDiffHelper>  EXCLUDE_CACHE = new ConcurrentHashMap<>();
    private static final List<Class<?>> SIMPLETYPE = Arrays.asList(Byte.class, Short.class,
            Integer.class, Long.class, Float.class, Double.class, Character.class,
            Boolean.class, String.class);

    public static boolean isSimpleField(Class<?> clazz) {
        return clazz.isPrimitive() || SIMPLETYPE.contains(clazz);
    }

    public static <T> EntityDiff<T> diff(T entity, T snapshot) {
        if (entity == null && snapshot == null) {
            throw new RuntimeException("params must not null");
        }

        final Class<T> nonNullEntity = (Class<T>) (entity == null ? snapshot : entity).getClass();
        final EnableEntityDiffHelper enableEntityDiffHelper = enableEntityDiffHelper(nonNullEntity);
        // 获取所有字段
        final Map<String, Field> fields = getAllFields(nonNullEntity);
        final Set<String> allFieldNames = fields.keySet();

        final EntityDiff<T> entityDiff = new EntityDiff<>(nonNullEntity, fields);
        for (String fieldName : allFieldNames) {
            try {
                final Field field = fields.get(fieldName);
                final Class<?> type = field.getType();
                if (enableEntityDiffHelper.isExclude(fieldName)) {
                    continue;
                }
                final Object entityValue = entity == null ? null : field.get(entity);
                final Object snapshotValue = snapshot == null ? null : field.get(snapshot);
                if (entityValue == null && snapshotValue == null) {
                    continue;
                }
                if (isSimpleField(type)) {
                    if (!Objects.deepEquals(entityValue, snapshotValue)) {
                        entityDiff.add(new EntityNode(type, fieldName, entityValue));
                    }
                } else if (Entity.class.isAssignableFrom(type)) {
                    final EntityDiff<?> childDiff = diff(entityValue, snapshotValue);
                    if (childDiff.isSelfModified()) {
                        entityDiff.add(new EntityNode(type, fieldName, childDiff));
                    }
                } else if (type.isArray()) {
                    final Object nonNull = entityValue == null ? snapshotValue : entityValue;
                    final Class<?> clazz = nonNull.getClass().getComponentType();
                    toArray(clazz, entityDiff, entityValue, snapshotValue, type, fieldName);
                } else {
                    if (entityValue instanceof Collection || snapshotValue instanceof Collection) {
                        final Class<?> clazz = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
                        toArray(clazz, entityDiff, entityValue, snapshotValue, type, fieldName);
                    } else if (entityValue instanceof Map) {
                        throw new RuntimeException("未想到这种情况的实际应用, 请联系开发者描述一下使用场景");
                    } else {
                        throw new RuntimeException("未想到这种情况的实际应用, 请联系开发者描述一下使用场景");
                    }
                }
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("获取属性进行比对发生异常: " + fieldName, e);
            }
        }
        return entityDiff;
    }

    private static <A, T> void toArray(Class<A> clazz, EntityDiff<T> entityDiff, Object entityValue, Object snapshotValue, Class<?> type, String fieldName) {
        final ArrayDiff<A> arrayDiff = new ArrayDiff<>(entityValue == null ? null : ((Collection<?>) entityValue).toArray(), snapshotValue == null ? null : ((Collection<?>) snapshotValue).toArray(), clazz);
        if (!arrayDiff.isEmpty()) {
            entityDiff.add(new EntityNode(type, fieldName, arrayDiff));
        }
    }

    private static Map<String, Field> getAllFields(Class<?> clazz) {
        return CACHE.computeIfAbsent(clazz, k -> {
            Map<String, Field> fieldMap = new HashMap<>(8);
            Class<?> cls = k;
            while (cls != Object.class) {
                Field[] fields = cls.getDeclaredFields();
                for (Field field : fields) {
                    // 一些通过字节码注入改写类的框架会合成一些字段，如 jacoco 的 $jacocoData 字段
                    // 正常情况下这些字段都需要被排除掉
                    if (!field.isSynthetic()) {
                        field.setAccessible(true);
                        fieldMap.put(field.getName(), field);
                    }
                }
                cls = cls.getSuperclass();
            }
            return fieldMap;
        });
    }

    private static <T> EnableEntityDiffHelper enableEntityDiffHelper(Class<T> clazz) {
        return EXCLUDE_CACHE.computeIfAbsent(clazz, k -> {
            final EnableEntityDiffHelper enableEntityDiffHelper = new EnableEntityDiffHelper();
            if (clazz.isAnnotationPresent(EnableEntityDiff.class)) {
                final EnableEntityDiff annotation = clazz.getAnnotation(EnableEntityDiff.class);
                enableEntityDiffHelper.process(annotation);
            }
            return enableEntityDiffHelper;
        });
    }
}
